package main

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/go-ini/ini"
	"github.com/kardianos/service"
)

func execdir() string {
	ex, _ := os.Executable()
	return filepath.Dir(ex)
}

var (
	RootDir = execdir()
	IsStop  = false
	Conf    = &Config{}
	Ch      chan *exec.Cmd
	FailNum = 0
)

type Server struct {
	Name string
	Disp string
	Desc string
}

type Command struct {
	Num     int
	Run     string
	Dir     string
	Stop    string
	MaxFail int
}
type Config struct {
	Server
	Command
}

func init() {
	//加载配置文件
	cfg, err := ini.Load(path.Join(RootDir, "conf.ini"))
	if err != nil {
		log.Fatal(err)
	}
	Conf.Server.Name = cfg.Section("server").Key("name").String()
	Conf.Server.Disp = cfg.Section("server").Key("disp").String()
	Conf.Server.Desc = cfg.Section("server").Key("desc").String()

	Conf.Command.Num = cfg.Section("command").Key("num").MustInt()
	Conf.Command.Run = cfg.Section("command").Key("run").String()
	Conf.Command.Dir = cfg.Section("command").Key("dir").String()
	Conf.Command.Stop = cfg.Section("command").Key("stop").String()
	Conf.Command.MaxFail = cfg.Section("command").Key("maxfail").MustInt()

	if len(Conf.Command.Dir) > 0 {
		os.Chdir(Conf.Command.Dir)
	}
}

func main() {
	logfile, _ := os.OpenFile(path.Join(RootDir, "server.log"), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
	log.SetOutput(logfile)
	Ch = make(chan *exec.Cmd, Conf.Command.Num)

	sconf := &service.Config{
		Name:        Conf.Server.Name,
		DisplayName: Conf.Server.Disp,
		Description: Conf.Server.Desc,
	}
	defer func() {
		logfile.Close()
		close(Ch)
	}()
	// 构建服务对象
	ws := &WinServer{}
	s, err := service.New(ws, sconf)
	if err != nil {
		log.Fatal(err)
	}

	if len(os.Args) == 2 {
		cmd := os.Args[1]
		if cmd == "-install" {
			if err = s.Install(); err != nil {
				fmt.Println(sconf.Name, "安装失败")
				log.Fatal(err)
			}
			fmt.Println(sconf.Name, "安装成功")
			return
		}
		if cmd == "-remove" {
			if err = s.Uninstall(); err != nil {
				fmt.Println(sconf.Name, "卸载失败")
				log.Fatal(err)
			}
			fmt.Println(sconf.Name, "卸载成功")
			return
		}

		if cmd == "-?" || cmd == "-h" {
			fmt.Println("窗口进程管理工具，使用[-install] [-remove]\n" +
				" -?,-h       帮助文档\n" +
				" -install,   安装服务\n" +
				" -remove,    卸载服务")
		}

		return
	}

	if err := s.Run(); err != nil {
		log.Fatal(err)
	}

}

type WinServer struct{}

func (ws *WinServer) Start(s service.Service) error {
	log.Println("启动服务")
	go ws.run(Conf.Command.Num)
	return nil
}

func (ws *WinServer) Stop(s service.Service) error {
	log.Println("终止服务")
	IsStop = true
	time.Sleep(200 * time.Millisecond)

	if flag := stopScript(); flag {
		return nil
	}
	// log.Println("进程数量：", len(Ch))
	length := len(Ch)
	for i := 0; i < length; i++ {
		cmd := <-Ch
		log.Println("终止进程id：", cmd.Process.Pid)
		err := cmd.Process.Kill()
		if err != nil {
			log.Println("终止服务错误：", err)
			return err
		}
	}

	return nil
}

func (ws *WinServer) run(num int) {
	fmt.Println("Start process num: ", num)
	var wg sync.WaitGroup
	for {
		if len(Ch) < num {
			wg.Add(1)
			go startScript(&wg)
			wg.Wait()
		}
		time.Sleep(time.Millisecond * 100)
	}
}

func startScript(wg *sync.WaitGroup) {
	execArr := strings.Split(strings.ReplaceAll(Conf.Command.Run, "  ", " "), " ")
	cmd := exec.Command(execArr[0], execArr[1:]...)
	if len(strings.TrimSpace(Conf.Command.Dir)) > 0 {
		cmd.Dir = Conf.Command.Dir
	}
	err := cmd.Start()

	if err != nil {
		wg.Done()
		fmt.Println("Error start service: ", err)
		log.Fatal(err)
		os.Exit(1)
	}

	log.Println("进程id：", cmd.Process.Pid)
	Ch <- cmd
	wg.Done()
	timeStart := time.Now()
	err = cmd.Wait()
	timeEnd := time.Now()
	//阻塞式进程，在杀掉进程会报错
	if err != nil {
		fmt.Println("Error running service: ", err)
		log.Println(err)
		if timeEnd.Sub(timeStart).Milliseconds() < int64(time.Millisecond*2000) {
			FailNum++
			fmt.Println("失败次数：", FailNum)
			log.Println("失败次数：", FailNum)
			if FailNum >= Conf.Command.MaxFail {
				log.Println("失败次数超过最大值：", Conf.Command.MaxFail, "，进程终止")
				os.Exit(1)
			}
		}
	}
	if !IsStop {
		<-Ch
	}
}

func stopScript() bool {
	if len(strings.TrimSpace(Conf.Command.Stop)) == 0 {
		return false
	}
	execArr := strings.Split(strings.ReplaceAll(Conf.Command.Stop, "  ", " "), " ")
	cmd := exec.Command(execArr[0], execArr[1:]...)
	if len(strings.TrimSpace(Conf.Command.Dir)) > 0 {
		cmd.Dir = Conf.Command.Dir
	}
	err := cmd.Run()
	if err != nil {
		fmt.Println("Error stop service: ", err)
		log.Fatal(err)
		return false
	}
	return true
}
